#ifndef MATRIX_H
#define MATRIX_H

#include <iostream>

/**
 * @file   main.cpp
 * @author Harrior <harrior@harrior-VirtualBox>
 * @date   Thu Oct  7 23:30:08 2021
 * 
 * @brief  This is a file that can achive the basic function of Matrix, including judging whether it
 * is a square and the multiplication between matrixs and even vectors.
 * 
 */

/**
 * 
 * This is the basic class of matrix.
 * 
 */
template <typename T>
class Matrix
{
public:
    
    int n_row;
    int n_col;
    T* element;

    //T0 construct a matrix
    Matrix()
    {
        n_col = 0;
        n_col = 0;
        element = NULL;
    }
    Matrix(int row, int col)
    {
        if(row == 0 || col == 0)
        {
            warning(0);
            exit;
        }
        n_row = row;
        n_col = col;
        element = new T[row * col];
        for(int i = 0; i < row; i++ )
	        for(int j = 0; j < col; j++)
		        std::cin >> element[i * col + j];
    }

    //To delete the space used
    ~Matrix()    
    {
        if (element != NULL)
            delete element;
        element = NULL;
        n_row = 0;
        n_col = 0;
    }
    
    //To help return error to operators.
    static void warning(int err);

    //To print out the elements of the Matrix.
    void printMatrix();

    //To judge whether the matrix is square.
    bool is_square();
    
    //To redefine the rule of '*'.
    template <class U>
    friend Matrix<U> operator*(const Matrix<U>& a, const Matrix<U>& b);

    //To redefine the rule of '=' so that the multiplication can be suitable.
    Matrix<T>& operator=(const Matrix<T>& _obj)
    {
        n_col = _obj.n_col;
        n_row = _obj.n_row;
        if(element != NULL)
            delete [] element;
        element = new T[n_col  * n_row];
        for(int i = 0; i < n_row; i++)
            for(int j = 0; j < n_col; j++)
            element[i * n_col + j] = _obj.element[i * n_col + j];
        return *this;
    }
};

/**
 * 
 * This is the class of RowVector.
 * 
 */
template <typename T>
class RowVector: public Matrix<T>
{
public:
    //To ensure how to creat and clear a rowvector.
    RowVector():Matrix<T>(){}
    RowVector(int col):Matrix<T>(1,col){}

    //Another case of '*'.
    template <class U>
    friend U operator*(const RowVector<U>& a, const RowVector<U>& b);
};

/**
 * 
 * This is the of ColVector.
 * 
 */
template <typename T>
class ColVector: public Matrix<T>
{
public:
    //To ensure how to creat and clear a colvector.
    ColVector():Matrix<T>(){}
    ColVector(int row):Matrix<T>(row,1){}
    
    //Tow other cases of '*'.
    template <class U>
    friend U operator*(const ColVector<U>& a, const ColVector<U>& b);
};

//Print out different words to show different falses.
template <typename T>
void Matrix<T>::warning(int err)
{
    switch (err)
    {
        case 0:
            std::cout << "Cannot creat." << std::endl;
            break;
        case 1:
            std::cout << "Cannot multiply these two matrixs." << std::endl;
            break;
        case 2:
            std::cout << "Cannot multiply these two vectors." << std::endl;
            break;
        case 3:
            std::cout << "Cannot fill the matrix." << std::endl;
        case 4:
            std::cout << "Out of range." << std::endl;
        default:
            std::cout << "Inexpectec error." << std::endl;
            break;
    }
}

//Judge whether the matrix is square.
template <typename T>
bool Matrix<T>::is_square()
{
    if(n_col == n_row)
        return true;
    return false;
}

//Judge whether the matrix is empty or not, if it is full, then print out.
template <typename T>
void Matrix<T>::printMatrix()
{
    if (element == NULL)
    {
        warning(4);
        exit;
    }
    for (int i = 0; i < n_row; i++)
    {
        for (int j = 0; j < n_col; j++)
            std::cout << element[i * n_col + j] << " ";
        std::cout << std::endl;
    }
}

//Redefine the rule of multiplication between matrixs and return a matrix.
template <typename T>
Matrix<T> operator*(const Matrix<T>& a, const Matrix<T>& b)
{
    if(a.n_col != b.n_row)
    {
        Matrix<T>::warning(1);
        exit;
    }
    Matrix<T> mul;
    mul.n_row = a.n_row;
    mul.n_col = b.n_col;
    mul.element = new T[a.n_row * b.n_col];
    for (int i = 0; i < a.n_row; i++)
        for (int j = 0; j < b.n_col; j++)
        {
            T part = 0;
            for (int k = 0; k < a.n_col; k++)
            {
                part += a.element[i * a.n_col + k] * b.element[k * b.n_col + j];
            }
            mul.element[i * mul.n_col + j] = part;
        }
    return mul;
}

//Redefine the rule of multiplication between RowVectors and return their intern product.
template <typename T>
T operator*(const RowVector<T>& a, const RowVector<T>& b)
{
    if(a.n_col != b.n_col)
    {
        Matrix<T>::warning(2);
        exit;
    }
    T sum = 0;
    for (int i = 0; i < a.n_col; i++)
    {
        sum += a.element[i] * b.element[i];
    }
    return sum;
}

//Redefine the rule of multiplication between ColVectors and return their intern product.
template <typename T>
T operator*(const ColVector<T>& a, const ColVector<T>& b)
{
    if(a.n_row != b.n_row)
    {
        Matrix<T>::warning(2);
        exit;
    }
    T sum = 0;
    for (int i = 0; i < a.n_row; i++)
    {
        sum += a.element[i] * b.element[i];
    }
    return sum;
}

//Redefine the rule of multiplication between RowVector and ColVector and return their intern product.
template <typename T>
T operator*(const RowVector<T>& a, const ColVector<T>& b)
{
    if(a.n_col != b.n_row)
    {
        Matrix<T>::warning(2);
        exit;
    }
    T sum = 0;
    for (int i = 0; i < a.n_col; i++)
    {
        sum += a.element[i] * b.element[i];
    }
    return sum;
}

#else
/// Do nothing
#endif
